/*
 * This file is part of algowala.
 *
 * algowala is copyright (c) 2012 Jagannathan Sampath <algowala@jagernot.com>
 *
 * algowala is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * algowala is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with algowala.  If not, see <http://www.gnu.org/licenses/>.
 *
*/

#include "input.h"
#include "curve_editor.h"
#include "curve_library.h"
#include "console.h"
#include "font.h"
#include "utils.h"
#include "random.h"
#include "vector2d.h"
#include "key_consts.h"
#include "ansi_color_codes.h"

#include <iostream>
using namespace std;

extern console cons;

extern int lmb, rmb;

console& operator<< (console& c, const hit_t& h) {
  const std::string what [] = {" nothing", " vertex", " L-tangent", " R-tangent"};
  c << h.crv->name << what[h.what] << ' ' << h.id;
  return c;
}

curve_editor::curve_editor (const std::string& settingsf) {

  curcrv = 0;

  use_existing_hit = false;
  hlid = 0;

  todo = DO_NOTHING;
  
  library = 0;

  load  (settingsf);
  
}

curve_editor::~curve_editor () {
  save ();
}

void curve_editor::load (const std::string& _fname) {

  // load settings from file

  ifstream file (_fname.c_str (), ios::in);
  if (!file) {
    cout << FAIL << "!!! cannot open curve editor settings from: " << _fname << " !!!" << ENDL;
    return;
  } else cout << LOAD << "<<< loading curve editor settings from: " << _fname;

  fname = _fname;

  std::string ignore;

  basic_viewer::load (file);

  int ct; file >> ignore >> ct;
  carry_tangents = (bool) ct;

  int mt; file >> ignore >> mt;
  mirror_tangents = (bool) mt;

  cout << ", done >>>" << ENDL;

}

void curve_editor::save () {

  // save settings to file
  ofstream file (fname.c_str(), ios::out);
  if (!file) {
    cout << "cannot save " << fname << endl;
    return;
  }

  basic_viewer::save (file);

  file << "carry_tangents " << carry_tangents << eol;
  file << "mirror_tangents " << mirror_tangents << eol;

}

bool curve_editor::handle_input () {

  basic_viewer::handle_input (); // pan, zoom, snap etc
  
  if (keypressed (k_f)) { // move vertex/tangent
    if (todo) {
      todo = DO_NOTHING;
      clear_hit (mov);
      return true;
    } else {
      mov = hittest ();
      if (mov()) {
        undos.push_back (undo_t (mov.crv_id, *mov.crv, win));
        curveinfo[mov.crv_id].lisner->selected (mov.crv_id);
        cons << console::green << "moving " << mov;
        if (keydown (k_lshift)) {
          todo = MOVE_ALL;
          cons << console::green << " and beyond";
        } else todo = MOVE_PICKED;
        cons << eol;
        return true;
      }
    }
  } else {
    switch (todo) {
      case DO_NOTHING:
        break;
      case MOVE_PICKED:
        move ();
        return true;
      case MOVE_ALL:
        move (1);
        return true;
    }
  }

  if (library) {
    if (library->num_curves ()) {
      if (keypressedd (k_9)) { load_curve (-1); return true; } else
      if (keypressedd (k_0)) { load_curve (+1); return true; } else
      if (keypressed (k_minus)) { library->del (); library->save (); return true; }
    }
    if (keypressed (k_equals)) { add_curve (); library->save (); return true; }
  }

  if (keypressed (k_v)) {

    if (keydown (k_lctrl)) { // l_ctrl + v ie paste
      pik = hittest ();
      paste (pik);
    } else { // just v ; delete vertex/tanget
      del = hittest ();
      if (del()) {
        remove ();
        clear_hit (del);
      }
    }
  }

  else if (keypressed (k_i)) { // i to pick, i again to insert
    if (num_curves () == 1) {
      if (!ins()) ins = hit_t (curveinfo[0].curve, 0);
      insert ();
      clear_hit (ins);
    } else {
      if (!ins()) {
        ins = hittest();
        if (ins()) cons << "press i again to insert vertex" << eol; else cons << console::red << "press i on a diamond to insert on that curve" << eol;
      } else {
        insert ();
        clear_hit (ins);
      }

    }
  }

  else if (keypressedd (k_z) && !mov()) { // undo & redo
    if (keydown (k_lshift)) // l_shift + z - redo
      dodo (redos, undos, "no more redos.");
    else
      dodo (undos, redos, "no more undos."); // just z, undo
  }

  else if (keypressed (k_r)) { // create hit list, pick the top
    use_existing_hit = false;
    hittest ();
    if (pik()) {
      pick (pik.crv_id);
      use_existing_hit = true;
      cons << console::green << "picked " << pik << " [" << hlid+1 << '/' << hitlist.size() << ']' << eol;
    }
  }

  else if (keypressed (k_left)) { // browse hit list
    int n = hitlist.size ();
    if (n) {
      int l = n - 1;
      if (--hlid < 0) hlid = l;
      pik = hitlist[hlid];
      calc_hit_params (pik);
      curveinfo [pik.crv_id].lisner->selected (pik.crv_id);
      use_existing_hit = true;
      cons << console::green << "picked " << pik << " [" << hlid+1 << '/' << hitlist.size() << ']' << eol;
    }
  }

  else if (keypressed (k_right)) { // browse hit list
    int n = hitlist.size();
    if (n) {
      int l = n - 1;
      if (++hlid > l) hlid = 0;
      pik = hitlist[hlid];
      calc_hit_params (pik);
      curveinfo [pik.crv_id].lisner->selected (pik.crv_id);
      use_existing_hit = true;
      cons << console::green << "picked " << pik << " [" << hlid+1 << '/' << hitlist.size() << ']' << eol;
    }
  }

  else if (keypressedd (k_t)) { // mirror about x
    mir = hittest ();
    if (mir()) {
      if (keydown (k_lshift))
        mirror (1);  // whole curve
      else
        mirror (); // only hit vertex
      clear_hit (mir);
    }
  }

  else if (keypressed (k_l)) {

    hit_t hit = hittest ();

    if (hit ()) {
      // turn hit curve into polyline by folding tangents into vertex
      // still bezier curve but looks like a polyline ie l_shift + k to go back to bezier curve
      //
      curve_info& ci = curveinfo [hit.crv_id];
      multi_curve& crv = *ci.curve;
      undos.push_back (undo_t (hit.crv_id, crv, win));
      if (keydown (k_lshift)) { // l_shift + l ie fold tangents of hit vertex only
        double vx, vy; crv.get_vertex (hit.id, vx, vy);
        crv.set_left_tangent (hit.id, vx, vy);
        crv.set_right_tangent (hit.id, vx, vy);
        crv.evaluate ();
      } else convert2_polyline (crv);
      ci.lisner->edited (this, hit.crv_id);
    } 
  }

  else if (keypressed (k_k)) {

    hit_t hit = hittest ();

    if (hit()) {
      curve_info& ci = curveinfo [hit.crv_id];
      multi_curve& crv = *ci.curve;
      undos.push_back (undo_t (hit.crv_id, crv, win));
      const point<double>& obj = obj_chunk;
      if (keydown (k_lshift)) { // unfurl tangents of hit vertex
        double vx, vy; crv.get_vertex (hit.id, vx, vy);
        crv.set_left_tangent (hit.id, vx - obj.x, vy);
        crv.set_right_tangent (hit.id, vx + obj.x, vy);
        crv.evaluate ();
      } else convert2_catmull_rom (crv, max (obj.x, obj.y)); // turn bezier curve into 'catmull-rom' spline; still a bezier curve
      ci.lisner->edited (this, hit.crv_id);
    }
  }

  else if (keypressed (k_m)) { // mirror tangents ie move one tangent and the other mirrors it
    mirror_tangents = !mirror_tangents;
    if (mirror_tangents) cons << console::yellow << "tangents are mirrored." << eol; else cons << console::yellow << "tangents are not mirrored." << eol;
  }

  else if (keypressedd (k_left_bracket)) { // decrease curve resolution
    hit_t hit = hittest ();
    if (hit()) {
      resolution (hit.crv_id, +1);
    }
  }

  else if (keypressedd (k_right_bracket)) { // increase currve resolution
    hit_t hit = hittest ();
    if (hit()) {
      resolution (hit.crv_id, -1);
    }
  }

  else if (keypressed (k_g)) { // draw scratch curve
    if (num_curves () == 1) {

      if (!rep()) {
        rep = hit_t (curveinfo[0].curve, 0);
        clear_scratch_curve ();
        cons << console::yellow << "g to add vertex, h to complete or abort" << eol;
      }

      show_scratch_curve = true;
      double sx, sy; snap (sx, sy);
      win_scratch_points.push_back (point<double> (sx, sy));
      double curvx, curvy; win2obj (sx, sy, curvx, curvy);
      curv_scratch_points.push_back (point<double>(curvx, curvy));

    } else {

      if (rep()) {
        show_scratch_curve = true;
        double sx, sy; snap (sx, sy);
        win_scratch_points.push_back (point<double> (sx, sy));
        double curvx, curvy; win2obj (sx, sy, curvx, curvy);
        curv_scratch_points.push_back (point<double>(curvx, curvy));
      } else {
        rep = hittest ();
        if (rep()) {
          clear_scratch_curve ();
          cons << console::yellow << "g to add vertex, h to complete or abort" << eol;
        }
      }
    }

  }

  else if (keypressed (k_h)) { // complete scratch curve
    if (rep()) {
      replace ();
      clear_hit (rep);
      show_scratch_curve = false;
    }
  }

  else if (keypressed (k_c)) {
    if (keydown (k_lctrl)) { // l_ctrl + c - copy curve
      hit_t hit = hittest ();
      if (hit()) {
        copy = *get_curve(hit.crv_id);
        cons << console::green << "copied curve." << eol;
      }
    } else { // carry tangents?
      carry_tangents = !carry_tangents;
      if (carry_tangents) cons << console::yellow << "vertices carry their tangents." << eol; else cons << console::yellow << "vertices desert their tangents" << eol;
    }
  }

  else if (keypressedd (k_semicolon)) { // change curve color
    hit_t hit = hittest ();
    if (hit()) get_curve(hit.crv_id)->set_color ();
  }

  else if (keypressed (k_ralt)) { // mark curve segments
    hit_t hit = hittest ();
    if (hit()) {
      curve_info& ci = curveinfo [hit.crv_id];
      ci.mark_segments = !ci.mark_segments;
    }
  }

  return true;

}

void curve_editor::pick (int i) {
  int ncurves = curveinfo.size ();
  if (i > -1 && i < ncurves) {
    curve_info& ci = curveinfo[i];
    ci.lisner->selected (i);
  }
}

void curve_editor::calc_hit_params (hit_t& hit) {

  if (!hit()) return;

  multi_curve* crv = get_curve(hit.crv_id);
  double vx, vy, lx, ly, rx, ry;
  int id = hit.id;
  crv->get_vertex (id, vx, vy);
  crv->get_left_tangent (id, lx, ly);
  crv->get_right_tangent (id, rx, ry);
  direction (hit.left_tangent.x, hit.left_tangent.y, vx, vy, lx, ly);
  direction (hit.right_tangent.x, hit.right_tangent.y, vx, vy, rx, ry);
  hit.left_tangent_magnitude = magnitude (hit.left_tangent.x, hit.left_tangent.y);
  hit.right_tangent_magnitude = magnitude (hit.right_tangent.x, hit.right_tangent.y);

}

void curve_editor::hittest (multi_curve* crv, int crv_id, const points_array& points, unsigned int what) {

  for (int i = 0, j = points.size (); i < j; ++i) {
    const point<double>& v = points[i];
    double vdx, vdy; obj2win (v, vdx, vdy);
    double d2 = distance2<double> (vdx, vdy, win.mousex, win.mousey);
    if (d2 <= win.handle_radius2) hitlist.push_back (hit_t (crv, crv_id, what, i));
  }
}

hit_t curve_editor::hittest () {

  hit_t hit;

  if (use_existing_hit) {
    use_existing_hit = false;
    return pik;
  } else {
    hitlist.clear ();
    hlid = 0;
  }

  for (int i = 0, j = curveinfo.size (); i < j; ++i) {
    curve_info& ci = curveinfo[i];
    if (ci.disabled == false) {
      multi_curve* crv = ci.curve;
      hittest (crv, i, crv->vertices, hit_t::VERTEX);
      hittest (crv, i, crv->left_tangents, hit_t::LEFT_TANGENT);
      hittest (crv, i, crv->right_tangents, hit_t::RIGHT_TANGENT);
    }
  }

  int n = hitlist.size();
  if (n) {
    pik = hitlist[0];
    calc_hit_params (pik);
  } else pik = hit_t ();

  return pik;

}

bool curve_editor::move (hit_t& hit, double x, double y) {

  curve_info& ci = curveinfo [hit.crv_id];
  multi_curve* crv = ci.curve;

  bool need_eval = false;

  switch (hit.what) {

    case hit_t::VERTEX:
        need_eval = crv->set_vertex (hit.id, x, y);
        if (carry_tangents && need_eval) {
          crv->set_left_tangent (hit.id, x + hit.left_tangent.x, y + hit.left_tangent.y);
          crv->set_right_tangent (hit.id, x + hit.right_tangent.x, y + hit.right_tangent.y);
        }
      break;

    case hit_t::LEFT_TANGENT:
        need_eval = crv->set_left_tangent (hit.id, x, y);
        if (mirror_tangents && need_eval) {
          double vx, vy, lvx, lvy;
          crv->get_vertex (hit.id, vx, vy);
          unit_vector (lvx, lvy, vx, vy, x, y);
          crv->set_right_tangent (hit.id, vx - hit.right_tangent_magnitude * lvx, vy - hit.right_tangent_magnitude * lvy);
        }
      break;

    case hit_t::RIGHT_TANGENT:
        need_eval = crv->set_right_tangent (hit.id, x, y);
        if (mirror_tangents && need_eval) {
          double vx, vy, rvx, rvy;
          crv->get_vertex (hit.id, vx, vy);
          unit_vector (rvx, rvy, vx, vy, x, y);
          crv->set_left_tangent (hit.id, vx - hit.left_tangent_magnitude * rvx, vy - hit.left_tangent_magnitude * rvy);
        }
      break;

  }

  if (need_eval) {
    crv->evaluate();
    ci.lisner->edited (this, hit.crv_id);
  }

  return need_eval;

}

bool curve_editor::move () {
  double sx, sy; snap (sx, sy);
  double cx, cy; win2obj (sx, sy, cx, cy);
  return move (mov, cx, cy);
}

bool curve_editor::move (int) {

  double sx, sy; snap (sx, sy);

  double cx, cy; win2obj (sx, sy, cx, cy);

  curve_info& ci = curveinfo [mov.crv_id];
  multi_curve* crv = ci.curve;

  double vx, vy;
  double dx, dy;

  switch (mov.what) {
    case hit_t::VERTEX:
        crv->get_vertex (mov.id, vx, vy);
        break;
    case hit_t::LEFT_TANGENT:
        crv->get_left_tangent (mov.id, vx, vy);
        break;
    case hit_t::RIGHT_TANGENT:
        crv->get_right_tangent (mov.id, vx, vy);
        break;
  }

  dx = cx - vx;
  dy = cy - vy;

  bool result = false;
  for (int i = mov.id, j = crv->num_vertices (); i < j; ++i) {
    crv->get_vertex (i, vx, vy);
    result |= crv->set_vertex (i, vx + dx, vy + dy);
    crv->get_left_tangent (i, vx, vy);
    result |= crv->set_left_tangent (i, vx + dx, vy + dy);
    crv->get_right_tangent (i, vx, vy);
    result |= crv->set_right_tangent (i, vx + dx, vy + dy);
  }

  if (result) {
    crv->evaluate();
    ci.lisner->edited (this, mov.crv_id);

  }

  return result;

}

void curve_editor::remove () {

  curve_info& ci = curveinfo [del.crv_id];
  multi_curve* crv = ci.curve;

  undos.push_back (undo_t (del.crv_id, *crv, win));

  if (crv->remove (del.id)) {
    crv->evaluate();
    ci.lisner->edited (this, del.crv_id);
  } else {
    cons << console::red << "cant delete curve: " << crv->name << eol;
    undos.pop_back ();
  }

}

void curve_editor::insert () {

  double sx, sy; snap (sx, sy);
  double cx, cy; win2obj (sx, sy, cx, cy);


  curve_info& ci = curveinfo [ins.crv_id];
  multi_curve* crv = ci.curve;
  undos.push_back (undo_t (ins.crv_id, *crv, win));

  const point<double>& obj = obj_chunk;
  if (crv->insert (cx, cy, obj.x, obj.y)) {
    crv->evaluate();
    ci.lisner->edited (this, ins.crv_id);
  } else undos.pop_back ();

}

void curve_editor::replace () {

  if (curv_scratch_points.size () > 1) {

    curve_info& ci = curveinfo [rep.crv_id];
    multi_curve& crv = *ci.curve;
    undos.push_back (undo_t (rep.crv_id, crv, win));

    create_polyline (crv, curv_scratch_points);
    const point<double>& obj = obj_chunk;
    convert2_catmull_rom (crv, max (obj.x, obj.y));

    ci.lisner->edited (this, rep.crv_id);

    cons << console::green << "applied scratch curve." << eol;

  } else cons << console::red << "aborted scratch curve" << eol;


}

void curve_editor::mirror (int whole_curve) {

  // mirrors vertex (or curve) about horizontal - useful when editing waveforms

  curve_info& ci = curveinfo [mir.crv_id];
  multi_curve* crv = ci.curve;
  undos.push_back (undo_t (mir.crv_id, *crv, win));
  double xx, yy;
  if (whole_curve) {
    int num_vertices = crv->num_vertices ();
    for (int i = 0; i < num_vertices; ++i) {
      crv->get_vertex (i, xx, yy); crv->set_vertex (i, xx, -yy);
      crv->get_left_tangent (i, xx, yy); crv->set_left_tangent (i, xx, -yy);
      crv->get_right_tangent (i, xx, yy); crv->set_right_tangent (i, xx, -yy);
    }

    crv->evaluate();
    ci.lisner->edited (this, mir.crv_id);

  } else {
    switch (mir.what) {
      case hit_t::VERTEX:
        crv->get_vertex (mir.id, xx, yy);
        break;
      case hit_t::LEFT_TANGENT:
        crv->get_left_tangent (mir.id, xx, yy);
        break;
      case hit_t::RIGHT_TANGENT:
        crv->get_right_tangent (mir.id, xx, yy);
        break;
    }
    move (mir, xx, -yy);
  }

}

void curve_editor::resolution (int i, int dir) {

  /*curve_info& ci = curveinfo [i];
  multi_curve* crv = ci.curve;
  double res = crv->get_resolution();
  double newres = res + dir * get_obj_resolution ();
  if (newres > 0) {
    crv->set_resolution (newres);
    crv->evaluate();
    ci.lisner->edited (this, i);
  }*/

}

void curve_editor::draw () {

  project ();
  
    draw_common ();
    draw_all ();

  unproject ();
  
}

void curve_editor::draw_common () {
  basic_viewer::draw ();
  mark_curve_segments ();
  if (show_scratch_curve) draw_scratch_curve ();
  selector.draw ();
}

void curve_editor::draw_scratch_curve () {

  glColor3f (1, 1, 0.7);
  glBegin (GL_LINE_STRIP);

  double x, y; snap (x, y);
  for (int i = 0, j = win_scratch_points.size (); i < j; ++i) {
    const point<double>& p = win_scratch_points[i];
    x = p.x; y = p.y;
    glVertex2f (x, y);
  }

  glVertex2f (x, y);

  double sx, sy; snap (sx, sy);
  glVertex2f (sx, sy);

  glEnd ();

}

void curve_editor::draw_all () {
  // draw curve profile, vertices, tangents and handles
  for (int i = 0, j = curveinfo.size(); i < j; ++i) {
    curve_info& ci = curveinfo[i];
    multi_curve* crv = ci.curve;
    if (ci.disabled) {
      glColor3f (0.2, 0.2, 0.2);
      draw_curve (crv);
      draw_vertices (crv);
    } else  {
      glColor3f (crv->r, crv->g, crv->b);
      if ((i == pik.crv_id) && (j > 1)) glLineWidth (2);
				draw_curve (crv);
				draw_vertices (crv);
				draw_tangents (crv);
      glLineWidth (1);
    }
  }	
}

void curve_editor::mark_curve_segments () {

  // drop vertical lines from each curve profile point (dx, dy) to (dx, 0)
  glColor3f (0.2, 0.2, 0.2);
  for (int i = 0, j = curveinfo.size(); i < j; ++i) {
    curve_info& ci = curveinfo[i];
    if (ci.mark_segments) {
      multi_curve* crv = ci.curve;
      vector<curve>& curv = crv->curv;
      for (int i = 0, j = curv.size(); i < j; ++i) {
        vector<crvpt>& vpts = curv[i].vpts;
        glBegin (GL_LINES);
        for (int p = 0, q = vpts.size (); p < q; ++p) {
          double dx, dy; obj2win (vpts[p].x, vpts[p].y, dx, dy);
          glVertex2f (dx, dy);
          glVertex2f (dx, 0);
        }
        glEnd ();
      }
    }
  }

}

void curve_editor::draw_handle (const point<double>& p) {
  double x, y; obj2win (p, x, y);
  double handle [] = {x - win.handle_radius, y, x, y + win.handle_radius, x + win.handle_radius, y, x, y - win.handle_radius};
  glBegin (GL_LINE_LOOP);
    glVertex2f (handle[0], handle[1]);
    glVertex2f (handle[2], handle[3]);
    glVertex2f (handle[4], handle[5]);
    glVertex2f (handle[6], handle[7]);
  glEnd ();
}

void curve_editor::draw_tangent (const point<double>& p, const point<double>& t) {
  double x, y; obj2win (p, x, y);
  double tx, ty; obj2win (t, tx, ty);
  glBegin (GL_LINES);
    glVertex2f (x, y);
    glVertex2f (tx, ty);
  glEnd ();

}

void curve_editor::draw_curve (multi_curve* crv) {

  vector<curve>& curv = crv->curv;
  for (int i = 0, j = curv.size(); i < j; ++i) {
    vector<crvpt>& vpts = curv[i].vpts;
    glBegin (GL_LINE_STRIP);
    for (int p = 0, q = vpts.size (); p < q; ++p) {
      double dx, dy; obj2win (vpts[p].x, vpts[p].y, dx, dy);
      glVertex2f (dx, dy);
    }
    glEnd ();
  }

}

void curve_editor::draw_vertices (multi_curve* crv) {

  const points_array& vertices = crv->vertices;
  for (int p = 0, q = vertices.size(); p < q; ++p) {
    const point<double>& v = vertices[p];
    draw_handle (v);
  }

}

void curve_editor::draw_tangents (multi_curve* crv) {

  // draw tangents of ith curve

  const points_array& vertices = crv->vertices;
  const points_array& left_tangents = crv->left_tangents;
  const points_array& right_tangents = crv->right_tangents;
  for (int p = 0, q = vertices.size(); p < q; ++p) {
    const point<double>& v = vertices[p];
    const point<double>& lt = left_tangents[p];
    const point<double>& rt = right_tangents[p];
    draw_tangent (v, lt);
    draw_tangent (v, rt);
    draw_handle (lt);
    draw_handle (rt);
  }

}

void curve_editor::dodo (list<undo_t>& do1, list<undo_t>& do2, std::string mesg) {
  if (do1.size() > 0) {
    undo_t& last = do1.back ();
    if (last.i < num_curves ()) {
      curve_info& ci = curveinfo[last.i];
      multi_curve* crv = ci.curve;
      do2.push_back (undo_t (last.i, *crv, win));
        std::string name; double r, g, b;
        crv->get_color (r, g, b);
          *crv = last.curve;
        crv->set_color (r, g, b);
      ci.lisner->edited (this, last.i);
      win = last.win;
      do1.pop_back ();
    }
    hitlist.clear ();
  } else cons << console::red << mesg << eol;

}

void curve_editor::add (multi_curve* crv, curve_listener* lsnr) {
  curveinfo.push_back (curve_info (crv, lsnr));
}

void curve_editor::clear () {
  curveinfo.clear ();
  hitlist.clear ();
  clear_hit (pik);
}

multi_curve* curve_editor::get_curve (int i) {
  if (i > -1 && i < (int) curveinfo.size()) {
    curve_info& ci = curveinfo [i];
    return ci.curve;
  }
  cout << "could not get curve " << i << eol;
  return 0;
}

int curve_editor::num_curves () {
  return curveinfo.size ();
}

curve_info& curve_editor::get_curve_info (int i) {
  return curveinfo[i];
}

void curve_editor::enter () {

  win.calc ();
  basic_viewer::calc_visual_params ();

}

void curve_editor::clear_scratch_curve () {
  win_scratch_points.clear ();
  curv_scratch_points.clear ();
  scratch_curve.clear ();
  show_scratch_curve = false;
}

void curve_editor::attach_library (curve_library* lib) {
  library = lib;
}

void curve_editor::bg () {
}

curve_info::curve_info  (multi_curve* c, curve_listener* l, bool dis) {
  curve = c;
  lisner = l;
  disabled = dis;
  mark_segments = false;
}

curve_info::curve_info () {
  curve = 0;
  lisner = 0;
  disabled = mark_segments = false;
}

hit_t::hit_t (multi_curve* cv, int cid, unsigned int w, int i)  {
  crv = cv;
  crv_id = cid;
  what = w;
  id = i;
}

void hit_t::clear () {
  crv = 0;
  crv_id = -1;
  what = NONE;
  id = -1;
}

int hit_t::operator() () {
  return (crv_id != -1);
}

void curve_editor::clear_hit (hit_t& h) {
  h.clear ();
  use_existing_hit = false;
}

void curve_editor::load_curve (int dir) {
  if (!pik()) pik = hit_t (curveinfo[0].curve, 0);
  multi_curve& crv = *pik.crv;
  undos.push_back (undo_t (pik.crv_id, crv, win));
    double cr, cg, cb; crv.get_color (cr, cg, cb);
      if (dir == 1) crv = library->next (); else crv = library->prev();
    crv.set_color (cr, cg, cb);
  curveinfo[pik.crv_id].lisner->edited (this, pik.crv_id);
}

void curve_editor::add_curve () {
  if (!pik()) pik = hit_t (curveinfo[0].curve, 0);
  curve_info& ci = curveinfo [pik.crv_id];
  library->add (*ci.curve);
}

void curve_editor::insert_curve () {
  if (!pik()) pik = hit_t (curveinfo[0].curve, 0);
  library->insert (*get_curve (pik.crv_id));
}

void curve_editor::replace_curve () {
  if (!pik()) pik = hit_t (curveinfo[0].curve, 0);
  library->replace (*get_curve (pik.crv_id));
}

void curve_editor::paste (hit_t& hit) {
  if (hit()) {
    curve_info& ci = curveinfo [hit.crv_id];
    multi_curve* crv = ci.curve;
    if (copy.num_vertices()) {
      undos.push_back (undo_t (hit.crv_id, *crv, win));
        std::string cname; double cr, cg, cb;
        crv->get_color (cr, cg, cb);
          *crv = copy;
        crv->set_color (cr, cg, cb);
      ci.lisner->edited (this, hit.crv_id);
      cons << console::green << "pasted curve." << eol;
    } else cons << console::red << "no copy curve." << eol;
  } else cons << console::red << "pick a curve to paste." << eol;
}

std::string curve_editor::selection () {
  std::stringstream ss;
  const char* what [] = {"invalid", "v", "lt", "rt"};
  for (int i = 0, j = hitlist.size (); i < j; ++i) {
    hit_t& h = hitlist[i];
    multi_curve* c = h.crv;
    double x, y;
    switch (h.what) {
      case hit_t::VERTEX:
        c->get_vertex (h.id, x, y);
        break;
      case hit_t::LEFT_TANGENT:
        c->get_left_tangent (h.id, x, y);
        break;
      case hit_t::RIGHT_TANGENT:
        c->get_right_tangent (h.id, x, y);
        break;
    }
    ss << '{' << c->name << ' ' << what[h.what] << ' ' << h.id << ' ' << x << ' ' << y << "} ";
  }
  return ss.str ();
}
